package com.github.aloxc.plugin;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.io.IOException;

public class TreeDragTest extends JFrame {

    TreeDragSource ds;

    TreeDropTarget dt;

    JTree tree;

    public TreeDragTest() {
        super("Rearrangeable Tree");
        setSize(300, 200);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        // If you want autoscrolling, use this line:
        tree = new AutoScrollingJTree();
        // Otherwise, use this line:
        // tree = new JTree();
        getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);

        // If we only support move operations...
        // ds = new TreeDragSource(tree, DnDConstants.ACTION_MOVE);
        ds = new TreeDragSource(tree, DnDConstants.ACTION_COPY_OR_MOVE);
        dt = new TreeDropTarget(tree);
        setVisible(true);
    }

    public class AutoScrollingJTree extends JTree implements Autoscroll {
        private int margin = 12;

        public AutoScrollingJTree() {
            super();
        }

        @Override
        public void autoscroll(Point p) {
            int realrow = getRowForLocation(p.x, p.y);
            Rectangle outer = getBounds();
            realrow = (p.y + outer.y <= margin ? realrow < 1 ? 0 : realrow - 1 : realrow < getRowCount() - 1 ? realrow + 1 : realrow);
            scrollRowToVisible(realrow);
        }
        @Override
        public Insets getAutoscrollInsets() {
            Rectangle outer = getBounds();
            Rectangle inner = getParent().getBounds();
            return new Insets(inner.y - outer.y + margin, inner.x - outer.x + margin, outer.height - inner.height - inner.y + outer.y + margin, outer.width - inner.width - inner.x + outer.x + margin);
        }

        // Use this method if you want to see the boundaries of the
        // autoscroll active region

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Rectangle outer = getBounds();
            Rectangle inner = getParent().getBounds();
            g.setColor(Color.red);
            g.drawRect(-outer.x + 12, -outer.y + 12, inner.width - 24, inner.height - 24);
        }

    }

    public static void main(String args[]) {
        new TreeDragTest();
    }
}

// TreeDragSource.java
// A drag source wrapper for a JTree. This class can be used to make
// a rearrangeable DnD tree with the TransferableTreeNode class as the
// transfer data type.

class TreeDragSource implements DragSourceListener, DragGestureListener {

    DragSource source;

    DragGestureRecognizer recognizer;

    TransferableTreeNode transferable;

    DefaultMutableTreeNode oldNode;

    JTree sourceTree;

    public TreeDragSource(JTree tree, int actions) {
        sourceTree = tree;
        source = new DragSource();
        recognizer = source.createDefaultDragGestureRecognizer(sourceTree, actions, this);
    }

    /*
     * Drag Gesture Handler
     */
    @Override
    public void dragGestureRecognized(DragGestureEvent dge) {
        TreePath path = sourceTree.getSelectionPath();
        if ((path == null) || (path.getPathCount() <= 1)) {
            // We can't move the root node or an empty selection
            return;
        }
        oldNode = (DefaultMutableTreeNode) path.getLastPathComponent();
        transferable = new TransferableTreeNode(path);
        source.startDrag(dge, DragSource.DefaultMoveNoDrop, transferable, this);

        // If you support dropping the node anywhere, you should probably
        // start with a valid move cursor:
        // source.startDrag(dge, DragSource.DefaultMoveDrop, transferable,
        // this);
    }

    /*
     * Drag Event Handlers
     */
    @Override
    public void dragEnter(DragSourceDragEvent dsde) {
    }

    @Override
    public void dragExit(DragSourceEvent dse) {
    }
    @Override
    public void dragOver(DragSourceDragEvent dsde) {
    }
    @Override
    public void dropActionChanged(DragSourceDragEvent dsde) {
        System.out.println("Action: " + dsde.getDropAction());
        System.out.println("Target Action: " + dsde.getTargetActions());
        System.out.println("User Action: " + dsde.getUserAction());
    }
    @Override
    public void dragDropEnd(DragSourceDropEvent dsde) {
        /*
         * to support move or copy, we have to check which occurred:
         */
        System.out.println("Drop Action: " + dsde.getDropAction());
        if (dsde.getDropSuccess() && (dsde.getDropAction() == DnDConstants.ACTION_MOVE)) {
            ((DefaultTreeModel) sourceTree.getModel()).removeNodeFromParent(oldNode);
        }

        /*
         * to support move only... if (dsde.getDropSuccess()) {
         * ((DefaultTreeModel
         * )sourceTree.getModel()).removeNodeFromParent(oldNode); }
         */
    }
}

// TreeDropTarget.java
// A quick DropTarget that's looking for drops from draggable JTrees.
//

class TreeDropTarget implements DropTargetListener {

    DropTarget target;

    JTree targetTree;

    public TreeDropTarget(JTree tree) {
        targetTree = tree;
        target = new DropTarget(targetTree, this);
    }

    /*
     * Drop Event Handlers
     */
    private TreeNode getNodeForEvent(DropTargetDragEvent dtde) {
        Point p = dtde.getLocation();
        DropTargetContext dtc = dtde.getDropTargetContext();
        JTree tree = (JTree) dtc.getComponent();
        TreePath path = tree.getClosestPathForLocation(p.x, p.y);
        return (TreeNode) path.getLastPathComponent();
    }
    @Override
    public void dragEnter(DropTargetDragEvent dtde) {
        TreeNode node = getNodeForEvent(dtde);
        if (node.isLeaf()) {
            dtde.rejectDrag();
        } else {
            // start by supporting move operations
            // dtde.acceptDrag(DnDConstants.ACTION_MOVE);
            dtde.acceptDrag(dtde.getDropAction());
        }
    }
    @Override
    public void dragOver(DropTargetDragEvent dtde) {
        TreeNode node = getNodeForEvent(dtde);
        if (node.isLeaf()) {
            dtde.rejectDrag();
        } else {
            // start by supporting move operations
            // dtde.acceptDrag(DnDConstants.ACTION_MOVE);
            dtde.acceptDrag(dtde.getDropAction());
        }
    }
    @Override
    public void dragExit(DropTargetEvent dte) {
    }
    @Override
    public void dropActionChanged(DropTargetDragEvent dtde) {
    }
    @Override
    public void drop(DropTargetDropEvent dtde) {
        Point pt = dtde.getLocation();
        DropTargetContext dtc = dtde.getDropTargetContext();
        JTree tree = (JTree) dtc.getComponent();
        TreePath parentpath = tree.getClosestPathForLocation(pt.x, pt.y);
        DefaultMutableTreeNode parent = (DefaultMutableTreeNode) parentpath.getLastPathComponent();
        if (parent.isLeaf()) {
            dtde.rejectDrop();
            return;
        }

        try {
            Transferable tr = dtde.getTransferable();
            DataFlavor[] flavors = tr.getTransferDataFlavors();
            for (int i = 0; i < flavors.length; i++) {
                if (tr.isDataFlavorSupported(flavors[i])) {
                    dtde.acceptDrop(dtde.getDropAction());
                    TreePath p = (TreePath) tr.getTransferData(flavors[i]);
                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) p.getLastPathComponent();
                    DefaultTreeModel model = (DefaultTreeModel) tree.getModel();
                    model.insertNodeInto(node, parent, 0);
                    dtde.dropComplete(true);
                    return;
                }
            }
            dtde.rejectDrop();
        } catch (Exception e) {
            e.printStackTrace();
            dtde.rejectDrop();
        }
    }
}

// TransferableTreeNode.java
// A Transferable TreePath to be used with Drag & Drop applications.
//

class TransferableTreeNode implements Transferable {

    public static DataFlavor TREE_PATH_FLAVOR = new DataFlavor(TreePath.class, "Tree Path");

    DataFlavor flavors[] = { TREE_PATH_FLAVOR };

    TreePath path;

    public TransferableTreeNode(TreePath tp) {
        path = tp;
    }
    @Override
    public synchronized DataFlavor[] getTransferDataFlavors() {
        return flavors;
    }
    @Override
    public boolean isDataFlavorSupported(DataFlavor flavor) {
        return (flavor.getRepresentationClass() == TreePath.class);
    }
    @Override
    public synchronized Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        if (isDataFlavorSupported(flavor)) {
            return (Object) path;
        } else {
            throw new UnsupportedFlavorException(flavor);
        }
    }
}